﻿using System;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.IO;
using System.Linq;
using System.Windows;
using System.Windows.Media;
using FormatManager.Serializer;
using GBlason.Common.Converter;
using GBlason.Common.CustomCommand;
using GBlason.Properties;
using GBlason.ViewModel.CoatOfArm;
using GBlason.ViewModel.Contract;
using GBlason.ViewModel.Control;

namespace GBlason.ViewModel.General
{
    /// <summary>
    /// Entry point of a Coat of Arm. Top of the tree. Handle the file, and the CoA root object, and the PropertiesVM, and the TreeViewVM
    /// </summary>
    public class GbsFileViewModel : INotifyPropertyChanged
    {
        #region constructor

        /// <summary>
        /// Initializes a new instance of the <see cref="GbsFileViewModel"/> class.
        /// </summary>
        /// <remarks>Increase the number of "new document" and affect the default file title to the new document</remarks>
        public GbsFileViewModel()
        {
            CommandsApplied = new CommandRepository();
            IconName = "pack://application:,,,/Pictures/Icons16/file_extension_ptb.png";
            CommandsApplied.CommandEvent += CommandsAppliedCommandEvent;
            AvailableZoom = new ObservableCollection<ZoomLevelViewModel>
                                {
                                    new ZoomLevelViewModel{Name=Resources.Zoom10, Value = 0.1},
                                    new ZoomLevelViewModel{Name = Resources.Zoom25, Value = 0.25},
                                    new ZoomLevelViewModel{Name = Resources.Zoom50, Value = 0.5},
                                    new ZoomLevelViewModel{Name = Resources.Zoom100, Value = 1},
                                    new ZoomLevelViewModel{Name = Resources.Zoom150, Value = 1.5},
                                    new ZoomLevelViewModel{Name = Resources.Zoom200, Value = 2},
                                    new ZoomLevelViewModel{Name = Resources.Zoom500, Value = 5},
                                    new ZoomLevelViewModel{Name = Resources.Zoom1000, Value = 10},
                                    //the last is the fit to screen to be sure it's changed when the RootCoatOfArm change
                                    new ZoomLevelViewModel{Name = Resources.ZoomFitToScreen, Value = 0}
                                };
        }

        public static GbsFileViewModel DefaultGbsFile
        {
            get { return new GbsFileViewModel(); }
        }


        /// <summary>
        /// Initializes a new instance of the <see cref="GbsFileViewModel"/> class.
        /// Used when opening a document by using the "Open" command
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        public GbsFileViewModel(String fileName)
            : this()
        {
            FileName = fileName;
        }

        #endregion

        #region Treeview

        private ObservableCollection<CoatOfArmComponent> _rootForTreeView;

        public ObservableCollection<CoatOfArmComponent> RootForTreeView
        {
            get
            {
                return
                    _rootForTreeView ?? (_rootForTreeView = new ObservableCollection<CoatOfArmComponent> { RootCoatOfArm });
            }
        }

        private CoatOfArmComponent _historySelection;
        /// <summary>
        /// Gets or sets the history selection, used for display purpose because the treeview is bound with all the opeed files, and we lost the last selection when changing files.
        /// </summary>
        /// <value>
        /// The history selection.
        /// </value>
        public CoatOfArmComponent HistorySelection
        {
            get { return _historySelection ?? (_historySelection = RootCoatOfArm); }
            set { _historySelection = value; }
        }

        private bool _isDisplayed;

        /// <summary>
        /// Gets or sets a value indicating whether this instance is displayed.
        /// </summary>
        /// <value>
        /// 	<c>true</c> if this instance is displayed; otherwise, <c>false</c>.
        /// </value>
        public bool IsDisplayed
        {
            get { return _isDisplayed; }
            set
            {
                if (value)
                    CurrentlySelectedComponent = HistorySelection;
                else
                {
                    HistorySelection = CurrentlySelectedComponent;
                }
                _isDisplayed = value;
            }
        }

        #endregion

        #region Commands
        private CommandRepository _commandsApplied;

        /// <summary>
        /// Gets or sets the repository of the commands applied.
        /// </summary>
        /// <value>
        /// The commands applied.
        /// </value>
        public CommandRepository CommandsApplied
        {
            get { return _commandsApplied; }
            set
            {
                if (value == _commandsApplied)
                    return;
                _commandsApplied = value;
                OnPropertyChanged("CommandsApplied");
            }
        }

        /// <summary>
        /// Commandses the applied command event.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.Collections.Specialized.NotifyCollectionChangedEventArgs"/> instance containing the event data.</param>
        void CommandsAppliedCommandEvent(object sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
        {
            OnPropertyChanged("IsUnsaved");
        }

        /// <summary>
        /// Define if the current file is in a new status compared to the last time it have been saved
        /// </summary>
        public Visibility IsUnsaved
        {
            get
            {
                if (CommandsApplied.LastSavedCommand == null || CommandsApplied.LastSavedCommand != CommandsApplied.LastUndoableCommand)
                    return Visibility.Visible;
                return Visibility.Hidden;
            }
        }

        /// <summary>
        /// Gets or sets the zoom level.
        /// </summary>
        /// <value>
        /// The zoom level.
        /// </value>
        public Double ZoomLevel
        {
            get { return _zoomLevel; }
            set
            {
                if (value == _zoomLevel) return;
                _zoomLevel = value;
                OnPropertyChanged("ZoomLevel");
            }
        }

        /// <summary>
        /// Gets or sets the available zoom.
        /// The available zoom is a static resource, and have a CoA related value (fit to screen) it should not be moved in an area not CoA dependent.
        /// </summary>
        /// <value>
        /// The available zooms.
        /// </value>
        public ObservableCollection<ZoomLevelViewModel> AvailableZoom { get; set; }

        /// <summary>
        /// The default ZoomLevel is 100%
        /// </summary>
        private Double _zoomLevel = 1;

        /// <summary>
        /// Gets or sets the display size of the CoA area.
        /// </summary>
        /// <value>
        /// The display size of the co A area.
        /// </value>
        public Size DisplayCoAAreaSize { get; set; }

        /// <summary>
        /// Ratioes to fit to screen.
        /// </summary>
        /// <returns></returns>
        public Double RatioToFitToScreen()
        {
            var dataRect = Geometry.Parse(RootCoatOfArm.CurrentShape.Geometry).Bounds;
            var objectSize = dataRect.Size;

            double ratioHeight = DisplayCoAAreaSize.Height / objectSize.Height;
            double ratioWidth = DisplayCoAAreaSize.Width / objectSize.Width;
            return Math.Min(ratioHeight, ratioWidth);
        }
        #endregion

        #region coat of arms

        private CoatOfArmViewModel _rootCoatOfArm;

        /// <summary>
        /// Gets or sets the root coat of arm. For this file
        /// </summary>
        /// <value>
        /// The root coat of arm.
        /// </value>
        public CoatOfArmViewModel RootCoatOfArm
        {
            get { return _rootCoatOfArm; }
            set
            {
                if (value == _rootCoatOfArm) return;
                _rootCoatOfArm = value;
                _rootCoatOfArm.ParentFile = this;
                OnPropertyChanged("RootCoatOfArm");
                OnPropertyChanged("RootForTreeView");
            }
        }


        /// <summary>
        /// Gets or sets the currently selected component. Used for the display of the properties
        /// Launch an update on he bindings, for all the children properties.
        /// </summary>
        /// <value>
        /// The currently selected component.
        /// </value>
        public CoatOfArmComponent CurrentlySelectedComponent
        {
            get { return _currentlySelectedComponent; }
            set
            {
                if (value == _currentlySelectedComponent) return;
                if (_currentlySelectedComponent != null)
                    _currentlySelectedComponent.IsSelected = false;
                _currentlySelectedComponent = value;
                if (_currentlySelectedComponent != null)
                    _currentlySelectedComponent.IsSelected = true;

                OnPropertyChanged("CurrentlySelectedComponent");
            }
        }
        private CoatOfArmComponent _currentlySelectedComponent;
        #endregion

        #region Tools and "from repo" building functions

        /// <summary>
        /// Centralized method to handle the opening of files (multi, single, new, dropping).
        /// Do a lot of operation on the file opening
        /// </summary>
        /// <param name="fileNames">The file names.</param>
        /// <param name="checkFormat">if set to <c>true</c> [check format].</param>
        /// <exception cref="FileNotFoundException">if the file can't be found</exception>
        /// <exception cref="InvalidOperationException">if an error occur while reading the format</exception>
        public static void OpenFiles(String[] fileNames, bool checkFormat = true)
        {
            var opener = new GbsManager();
            foreach (var fileName in fileNames)
            {
                var loadGbsFile = opener.LoadGbsFile(fileName, checkFormat);
                var vmFile = new GbsFileViewModel(Path.GetFileNameWithoutExtension(fileName))
                {
                    FullFileName = fileName,
                    RootCoatOfArm = loadGbsFile.XmlCoatOfArms.ConvertToViewModel()
                };
                vmFile.CurrentlySelectedComponent = vmFile.RootCoatOfArm;
                //si le fichier est déjà ouvert, on ne fait que lui donner le focus (currently displayed), sinon on l'ajoute bien entendu ^^
                var alreadyOpenedFile =
                    GlobalApplicationViewModel.GetApplicationViewModel.OpenedFiles.FirstOrDefault(
                        vmExistingFile => vmExistingFile.FileName == vmFile.FileName);
                if (alreadyOpenedFile != default(GbsFileViewModel))
                {
                    GlobalApplicationViewModel.GetApplicationViewModel.CurrentlyDisplayedFile = alreadyOpenedFile;
                }
                else
                {
                    GlobalApplicationViewModel.GetApplicationViewModel.OpenedFiles.Add(vmFile);
                    GlobalApplicationViewModel.GetApplicationViewModel.CurrentlyDisplayedFile = vmFile;
                }
                //sauvegarde des fichiers recents - sous traités
                GlobalApplicationViewModel.GetApplicationViewModel.SaveOpenedOrSavedFileAsRecent(fileName);
            }
        }

        public void SaveThisFile(String fileName, bool checkFormat = true)
        {
            var saver = new GbsManager();
            saver.SaveAsGbs(RootCoatOfArm.ConvertToCoatOfArms(), fileName, checkFormat);
            FullFileName = fileName;
            FileName = Path.GetFileNameWithoutExtension(fileName);
            //sauvegarde des fichiers recents - sous traités
            GlobalApplicationViewModel.GetApplicationViewModel.SaveOpenedOrSavedFileAsRecent(fileName);
            CommandsApplied.LastSavedCommand = CommandsApplied.LastUndoableCommand;
            OnPropertyChanged("IsUnsaved");
        }

        /// <summary>
        /// Creates a new File
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <returns></returns>
        public static GbsFileViewModel CreateNew(String fileName)
        {
            var retour = new GbsFileViewModel { FileName = fileName, RootCoatOfArm = new CoatOfArmViewModel() };
            retour.CurrentlySelectedComponent = retour.RootCoatOfArm;
            return retour;
        }
        #endregion

        #region file properties

        public String FullFileName { get; set; }

        public String FileName
        {
            get { return _fileName; }
            set
            {
                if (value == _fileName) return;
                _fileName = value;
                OnPropertyChanged("FileName");
            }
        }

        private String _fileName;

        private String _iconName;

        public String IconName
        {
            get { return _iconName; }
            set
            {
                if (_iconName == value) return;
                _iconName = value;
                OnPropertyChanged("IconName");
            }
        }

        #endregion

        #region INotifyPropertyChanged Members

        private void OnPropertyChanged(string propertyName)
        {
            if (PropertyChanged != null)
                PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
        }

        public event PropertyChangedEventHandler PropertyChanged;

        #endregion
    }
}
